/*
 * Huffman.cpp
 *
 *  Created on: 27 de Mai de 2011
 *      Author: ei10139
 */

#include "Huffman.h"

Huffman::Huffman(const char *fileNameSource, const char *fileNameOutput)
{
    fileSource = fopen(fileNameSource, "rb");
    psourSize = get_file_size(fileSource);
    psour = new unsigned char[psourSize];
    psourSize = (int)fread(psour, 1, psourSize, fileSource);
    this->fileNameInput = fileNameSource;
    this->fileNameOutput = fileNameOutput;
}

Huffman::~Huffman()
{
	delete psour;
}

int Huffman::get_file_size(FILE *fp)
{
	fseek(fp, 0, SEEK_END);
	int size = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	return size;
}

void Huffman::countChars()
{
	for(unsigned int i = 0; i < 256; i++)
		charSum[i] = 0;

	for(unsigned int i = 0; i < psourSize; ++i)
		++charSum[(unsigned char)psour[i]];
}

void Huffman::compress()
{
    try
    {
    	// 1. Contar a ocorrencia de caracteres
    	countChars();

    	// 2. Colocar cada elemento num No e fazer a ordenacao dos elementos por ordem crescente
    	for(unsigned int i = 0; i < 256; i++)
		{
			if(charSum[i] == 0)
				continue;

			No * o = new No(i, charSum[i]);
			charSum[i] = caracteres.size();
			freq_caracteres.push(o);
			caracteres.push_back(o);
		}

#ifdef __DEBUG__
    	cout << "Tenho " << freq_caracteres.size() << " caracteres!" << endl;

    	const No * myNo = freq_caracteres.top();
    	cout << "Ocorrencia menor " << myNo->getFreq() << " corresponde ao caracter " << (char)(myNo->ch) << endl;
#endif

    	// 3. Huffmanzite it
    	while(freq_caracteres.size() > 1)
    	{
    		No * no1 = freq_caracteres.top();
    		freq_caracteres.pop();
    		No * no2 = freq_caracteres.top();
    		freq_caracteres.pop();

    		No * res = new No(no1->getFreq() + no2->getFreq());

    		res->setLeftNo(no1);
    		res->setRightNo(no2);

    		freq_caracteres.push(res);
    	}
    	root = freq_caracteres.top();
    	store();


    }catch(...)
    {
    	throw new IOError();
    }
}

void Huffman::decompress()
{
	load();
	try
	{
		No * noNow = root;
		char * pout = new char[256 * 256];
		unsigned int poutOffset = 0, fileOffset = 0;
		FILE * pFile = fopen( fileNameOutput, "wb" );
		while(!bs.eos())
		{
			noNow = bs.get() ? noNow->getRightNo() : noNow->getLeftNo();

			if(noNow->hasNos)
				continue;

			//cout << (char)noNow->ch;
			pout[poutOffset] = (char) noNow->ch;
			if(++poutOffset >= 256 * 256)
			{
				fwrite(pout, 1, 256 * 256, pFile);
				poutOffset = 0;
			}

			noNow = root;
			if(poutSize == ++fileOffset)
				break;
		}
		fwrite(pout, 1 , poutOffset, pFile);
		fclose(pFile);

		cout << endl;
		if(noNow != root)
			cout << "Provavelmente a string esta errada." << endl;
		else
			cout << "Provavelmente a string esta certa." << endl;
	}catch(...)
	{
		throw new IOError();
	}
}

void Huffman::store()
{
    ofstream* file = new ofstream();
    file->open(fileNameOutput, ios::out | ios::binary);
    file->write(new char(psourSize >> 24), 1);
    file->write(new char(psourSize >> 16), 1);
    file->write(new char(psourSize >> 8), 1);
    file->write(new char(psourSize), 1);
    No::store(file, caracteres, freq_caracteres.top());
	for(unsigned long i = 0; i < psourSize; ++i)
	{
#ifdef __DEBUG__
		if(i % 1000000 == 0)
			cout << i << endl;
#endif

		try
		{
			bs.write(caracteres[charSum[(unsigned char)psour[i]]]->getCode());
		} catch(BitStreamFull * bsf)
		{
			delete bsf;
			bs.store(file);
		}
	}

	bs.store(file);
	file->close();
}

void Huffman::load()
{
	ifstream file;
	root = new No(0);
	char * c = new char();
	char * csize = new char[4];
	caracteres.clear();
	cout << "Loading file " << fileNameInput << endl;

    file.open(fileNameInput, ios::in | ios::binary);
	file.read(csize, 4);
	poutSize = (((unsigned char)csize[0]) << 24)
			 + (((unsigned char)csize[1]) << 16)
			 + (((unsigned char)csize[2]) << 8)
			 +  ((unsigned char)csize[3]);

	No::load(&file, &caracteres, root);
	while(!file.eof())
	{
		file.read(c, 1);
		bs.write((unsigned char) *c);
	}
	file.close();
}
